package cn.ch.basic.service.impl;

import cn.ch.basic.constant.WeChatConstant;
import cn.ch.basic.dto.LoginDto;
import cn.ch.basic.exception.BusinessException;
import cn.ch.basic.jwt.JwtUtils;
import cn.ch.basic.jwt.LoginData;
import cn.ch.basic.service.ILoginService;
import cn.ch.basic.util.*;
import cn.ch.org.domain.Employee;
import cn.ch.org.mapper.EmployeeMapper;
import cn.ch.system.domain.Menu;
import cn.ch.user.domain.Logininfo;
import cn.ch.user.domain.User;
import cn.ch.user.domain.Wxuser;
import cn.ch.user.mapper.LogininfoMapper;
import cn.ch.user.mapper.UserMapper;
import cn.ch.user.mapper.WxuserMapper;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.security.PrivateKey;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
@Slf4j
public class LoginServiceImpl implements ILoginService {
    @Autowired
    private LogininfoMapper logininfoMapper;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private WxuserMapper wxuserMapper;
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private EmployeeMapper employeeMapper;

    @Value("${jwt.rsa.pri}")//这里用来spring的El表达式，从.yml文件里面去读取要用的配置
    private String priKey;

    /**
     * 用来验证用户登录
     * @param dto
     * @return
     */
    @Override
    public Map<String, Object> account(LoginDto dto) {
        //1.参数校验
        if (StringUtils.isEmpty(dto.getUsername())
        ||StringUtils.isEmpty(dto.getType())
        ||StringUtils.isEmpty(dto.getPassword())
        )
        {
            throw new BusinessException("参数出现错误！！");
        }
        //2.根据username和type查询t_logininfo表，同时比较username，password，email
        Logininfo logininfo = logininfoMapper.loadByDto(dto);//获取账号
        if (logininfo == null) {
            throw new BusinessException("账号不存在！！");
        }
        //3.验证密码是否一致
        String salt = logininfo.getSalt();//获取盐值
        String password = MD5Utils.encrypByMd5(dto.getPassword() + salt);//加密后的密码
        if(!password.equals(logininfo.getPassword())){//比较后台的密码
            throw new BusinessException("密码不正确");
        }
        //4.判断用户是否被禁用了
        Integer disable = logininfo.getDisable();
        if(disable.intValue() == 0){
            throw new BusinessException("该用户已经被禁用了！");
        }
        //5.将登录的信息存储到redis里面, 6.返回token + logininfo, 7.返回map
        return loginJwtSuccess(logininfo);//把map数据返回到前台
    }



    /**
     * 微信扫码登录实现
     * @param param
     * @return
     */
    @Override
    public AjaxResult wechat(Map<String, String> param) {
        //1.接收code，做校验，因为接收到数据就存在code，所以code不会为空
        String code = param.get("code");

        //2.根据code从微信去获取token + openId ,将URL地址替换成自己的
        String tokenUrl = WeChatConstant.LoginConstant.TOKEN_URL
                .replace("APPID", WeChatConstant.LoginConstant.APPID)
                .replace("SECRET", WeChatConstant.LoginConstant.SECRET)
                .replace("CODE", code);
        //2.1将获取到的地址转换成字符串，调用HttpClientUtils工具类
        String tokenStr = HttpClientUtils.httpGet(tokenUrl);
        //2.2将字符串转换成JSON对象，里面包含access_token、openid等
        JSONObject jsonObject = JSONObject.parseObject(tokenStr);
        //2.3获取到token
        String accessToken = jsonObject.getString("access_token");
        //2.4获取到openid
        String openId = jsonObject.getString("openid");

        //3.根据openId查询本地的t_wxuser数据，验证是否曾今已经扫码过
        Wxuser wxuser = wxuserMapper.loadByOpenId(openId);
        //3.1如果查询到的wxuser和user_id不为空，说明已经有数据是已经绑定过微信的
        if (wxuser != null && wxuser.getUser_id()!= null){
            System.out.println(wxuser.getUser_id());

            //4.直接免密登录
            //4.1根据wxuser里面的user_id，再来查询logininfo里面的user_id。两次一起查（连表查询），节省资源
            Logininfo logininfo = logininfoMapper.loadByUserId(wxuser.getUser_id());

            //4.5返回数据
            //return AjaxResult.me().setSuccess(true).setResultObj(map);
            return AjaxResult.me().setSuccess(true).setResultObj(loginJwtSuccess(logininfo));

        }
        //5.没有绑定过微信，需要走微信绑定流程，同时需要返回：token + openId
        String binderParams = "?accessToken="+accessToken+"&openId="+openId;//里面的样式与前端页面相关，
        //返回数据到
        return AjaxResult.me().setSuccess(false).setResultObj(binderParams);

    }

    /**
     * 微信绑定流程
     * @param param
     * @return
     */
    @Override
    public Map<String, Object> binder(Map<String, String> param) {
        //1.获取前端传入过来的数据，因为前端就设置了值，所有可以不用参数校验
        String phone = param.get("phone");
        String verifyCode = param.get("verifyCode");
        String accessToken = param.get("accessToken");
        String openId = param.get("openId");

        //2.校验验证码是否正确 格式：binder:phone
        String msgKey = WeChatConstant.LoginConstant.BUSSINESS_BINDER_PREFIX+phone;
        //2.1根据存储的key，来查对应code
        Object msgCode = redisTemplate.opsForValue().get(msgKey);
        //2.2验证验证码是否正确，是否过期 (code+时间戳)
        if(msgCode == null){
            throw new BusinessException("验证码已失效，请重新获取！！");
        }
        //2.3使用凭借之前的验证码（msgCode）
        String codeTmp = msgCode.toString().split(":")[0];
        if(!verifyCode.equalsIgnoreCase(codeTmp)){
            throw new BusinessException("请填写正确的验证码！！");
        }

        //3.验证手机号是否曾经被注册过
        //3.1根据手机号查询对应的user信息
        User user = userMapper.loadByPhone(phone);
        //3.2先设置logininfo的初始值，之后在查询出来的时候再重写赋值
        Logininfo logininfo = null;
        //3.3如果user不为空，说明phone注册过，可以直接根据id来查找
        if(user != null){
            //3.4根据user表里面的id来查找logininfo表 对应的数据
            logininfo = logininfoMapper.loadByUserId(user.getId());
        }else{
        //4.没有被注册过，我们需要去保存（绑定传入过来的数据），然后联级保存到user表和logininfo表（用之前的联级保存方法在employeeserviceimpl）
           //4.1初始化phone对应的user信息
            user = initUser(phone);
            //4.2把获取并添加的user信息，同步到logininfo里面
            logininfo = user2Logininfo(user);
            //4.3logininfo添加到数据库
            logininfoMapper.save(logininfo);
            //4.4返回自增ID
            user.setLogininfoId(logininfo.getId());
            userMapper.save(user);
        }
        //5.根据前端传入的token和openId 查询微信用户信息，替换为自己的url路径
        String wxUserUrl = WeChatConstant.LoginConstant.WXUSER_URL
                .replace("ACCESS_TOKEN",accessToken)
                .replace("OPENID",openId);
        //5.1把自己的地址通过工具类转换成字符串
        String wxUserStr = HttpClientUtils.httpGet(wxUserUrl);
        //5.2 根据微信返回的信息创建t_wxuser，设置初始值.因为
        Wxuser wxuser = JSONObject.parseObject(wxUserStr, Wxuser.class);
        //5.3 把t_wxuser绑定平台user_id
        wxuser.setUserId(user.getId());
        wxuserMapper.save(wxuser);
        
        //6.免密登录
        return loginJwtSuccess(logininfo);
    }

    /**
     * 把user中的数据传到logininfo里面
     * @param user
     * @return
     */
    private Logininfo user2Logininfo(User user) {
        //创建一个logininfo对象
        Logininfo logininfo = new Logininfo();
        //使用 BeanUtils.copyProperties，将user类转换成logininfo类
        BeanUtils.copyProperties(user, logininfo);
        //type必须要设置，这是为了区分user和employee
        logininfo.setType(1);
        return logininfo;
    }

    /**
     * 初始化user对象
     * @param phone
     * @return
     */
    private User initUser(String phone) {
        //创建对象
        User user = new User();
        //设置用户名
        user.setUsername(phone);
        //设置手机号
        user.setPhone(phone);
        //盐值
        String salt = StrUtils.getComplexRandomString(32);
        //设置一个默认密码
        String md5Pwd = MD5Utils.encrypByMd5("123" + salt);
        user.setSalt(salt);
        user.setPassword(md5Pwd);
        return user;
    }

    /**
     * 第一种，以前使用的方法，将数据存储到redis。
     * 用户免密功能，需要获取用户基本信息，logininfo
     * @param logininfo
     * @return
     */
   /* private Map<String, Object> noPassLogin(Logininfo logininfo){
        //4.2生成随机字符串，作为redis存储的key
        String token = UUID.randomUUID().toString();
        //4.3把logininfo存储到redis里面
        redisTemplate.opsForValue().set(
                token,
                logininfo,
                30,
                TimeUnit.MINUTES);
        //4.4返回token + logininfo，这里用HashMap来接收
        Map<String,Object> map = new HashMap<>();
        //把token添加到map
        map.put("token", token);
        //返回数据的时候要考虑，密码和盐值等，是私密的
        logininfo.setSalt(null);
        logininfo.setPassword(null);
        //把logininfo添加到map
        map.put("logininfo", logininfo);
        return map;
    }*/

    /**
     * 使用jwt的方式存储数据，直接将数据封装成加密的字符串，返回给前端
     * @param logininfo
     * @return
     */
    public Map<String, Object> loginJwtSuccess(Logininfo logininfo){
        try {
            LoginData loginData = new LoginData();
            if(logininfo.getType().intValue() == 1){//查询用户信息
                User user = userMapper.loadUserByLogininfoId(logininfo.getId());
                user.setSalt(null); //加密
                user.setPassword(null);
                loginData.setUser(user);
            }else{//查询员工信息 + 对应的权限信息 + 对应的菜单信息
                //1.查询对应的员工信息
                Employee employee = employeeMapper.loadEmployeeByLogininfoId(logininfo.getId());
                employee.setSalt(null);
                employee.setPassword(null);
                //2.把信息添加到Admin里面
                loginData.setAdmin(employee);
                //3.
                List<String> snList = employeeMapper.loadlogininfo2Permission(logininfo.getId());
                //4.通过logininfo查询对应的菜单
                List<Menu> menus = employeeMapper.loadMenusByLogininfoId(logininfo.getId());
                loginData.setPermissions(snList);//当前登录人所拥有的的按钮权限
                loginData.setMenus(menus);//当前登录人所拥有的菜单
            }
            //这里使用私钥来加密，使用公钥来解密，保证获取到的admin的信息安全
            //获取私钥 ,使用
            PrivateKey privateKey = RsaUtils.getPrivateKey(JwtUtils.class.getClassLoader().getResource(priKey).getFile());
            //使用工具类进行加密，得到jwt
            String jwtStr = JwtUtils.generateTokenExpireInMinutes(loginData, privateKey, 60);
            //使用log4j来打印
            log.info("您的jwt是：{}",jwtStr);
            Map<String, Object> map = new HashMap<>();
            map.put("token",jwtStr);
            //返回跟权限相关的数据
            if(loginData.getUser() != null){
                map.put("user",loginData.getUser());
            }else{
                map.put("user",loginData.getAdmin());
            }
            //前端需要做动态菜单显示
            map.put("menus",loginData.getMenus());
            //前端需要做动态按钮显示
            map.put("permissions",loginData.getPermissions());
            return map;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}